home *** CD-ROM | disk | FTP | other *** search
/ Power Programmierung / Power-Programmierung (Tewi)(1994).iso / mutt / gomoku.mut < prev    next >
Lisp/Scheme  |  1988-09-26  |  44KB  |  1,343 lines

  1. ;;   Once installed and compiled, the program is invoked with 'M-x gomoku'
  2. ;; and 'C-h m' (the well-known describe-mode) will list all key bindings
  3. ;; provided to the player.  Have fun.
  4.  
  5. ;;; Gomoku game between you and GNU Emacs.  Last modified on 13 Sep 1988
  6. ;;; Converted to Mutt 9/88 C Durland
  7. ;;;
  8. ;;; Written by Ph. Schnoebelen (phs@lifia.imag.fr), 1987, 1988
  9. ;;; with precious advices from J.-F. Rit.
  10. ;;; This has been tested with GNU Emacs 18.50.
  11. ;;;
  12. ;;; This software is distributed 'as is', without warranties of any
  13. ;;; kind, but all comments, suggestions and bug reports are welcome.
  14.  
  15.  
  16. ;; RULES:
  17. ;;
  18. ;; Gomoku is a game played between two players on a rectangular board.    Each
  19. ;; player, in turn, marks a free square of its choice. The winner is the first
  20. ;; one to mark five contiguous squares in any direction (horizontally,
  21. ;; vertically or diagonally).
  22. ;;
  23. ;; I have been told that, in "The TRUE Gomoku", some restrictions are made
  24. ;; about the squares where one may play, or else there is a known forced win
  25. ;; for the first player. This program has no such restriction, but it does not
  26. ;; know about the forced win, nor do I.     Furthermore, you probably do not know
  27. ;; it yourself :-).
  28.  
  29.  
  30. ;; HOW TO INSTALL:
  31. ;;
  32. ;; There is nothing specific w.r.t. installation: just put this file in the
  33. ;; lisp directory and add an autoload for command gomoku in site-init.el. If
  34. ;; you don't want to rebuild Emacs, then every single user interested in
  35. ;; Gomoku will have to put the autoload command in its .emacs file.  Another
  36. ;; possibility is to define in your .emacs some command using (require
  37. ;; 'gomoku).
  38. ;;
  39. ;; The most important thing is to BYTE-COMPILE gomoku.el because it is
  40. ;; important that the code be as fast as possible.
  41. ;;
  42. ;; There are two main places where you may want to customize the program: key
  43. ;; bindings and board display. These features are commented in the code. Go
  44. ;; and see.
  45.  
  46.  
  47. ;; HOW TO USE:
  48. ;;
  49. ;; Once this file has been installed, the command "M-x gomoku" will display a
  50. ;; board, the size of which depends on the size of the current window. The
  51. ;; size of the board is easily modified by giving numeric arguments to the
  52. ;; gomoku command and/or by customizing the displaying parameters.
  53. ;;
  54. ;; Emacs plays when it is its turn. When it is your turn, just put the cursor
  55. ;; on the square where you want to play and hit RET, or X, or whatever key you
  56. ;; bind to the command gomoku-human-plays. When it is your turn, Emacs is
  57. ;; idle: you may switch buffers, read your mail, ... Just come back to the
  58. ;; *Gomoku* buffer and resume play.
  59.  
  60.  
  61. ;; ALGORITHM:
  62. ;;
  63. ;; The algorithm is briefly described in section "THE SCORE TABLE". Some
  64. ;; parameters may be modified if you want to change the style exhibited by the
  65. ;; program.
  66.  
  67.  
  68. (include me.h)
  69. (include mod.mut)
  70. (include random.mut)
  71. (include max.mut)
  72. (include min.mut)
  73.  
  74. ;;;
  75. ;;; GOMOKU MODE AND KEYMAP.
  76. ;;;
  77.  
  78. (include nomunge.mut)
  79.  
  80. (defun create-gomoku-mode-map
  81. {
  82.   (buffer-nomunge)
  83.  
  84.   ;; Key bindings for cursor motion. Arrow keys are just "function"
  85.   ;; keys, see below.
  86.   (bind-local-key "gomoku-move-nw"    "y")        ; Y
  87.   (bind-local-key "gomoku-move-ne"    "u")        ; U
  88.   (bind-local-key "gomoku-move-sw"    "b")        ; B
  89.   (bind-local-key "gomoku-move-se"    "n")        ; N
  90.   (bind-local-key "gomoku-move-left"    "h")        ; H
  91.   (bind-local-key "gomoku-move-right"    "l")        ; L
  92.   (bind-local-key "gomoku-move-down"    "j")        ; J
  93.   (bind-local-key "gomoku-move-up"    "k")        ; K
  94.   (bind-local-key "gomoku-move-down"    "C-n")        ; C-N
  95.   (bind-local-key "gomoku-move-down"    "F-D")        ; down arrow
  96.   (bind-local-key "gomoku-move-up"    "C-p")        ; C-P
  97.   (bind-local-key "gomoku-move-up"    "F-C")        ; up arrow
  98.   (bind-local-key "gomoku-move-right"    "C-f")        ; C-F
  99.   (bind-local-key "gomoku-move-right"    "F-E")        ; right arrow
  100.   (bind-local-key "gomoku-move-left"    "C-b")        ; C-B
  101.   (bind-local-key "gomoku-move-left"    "F-F")        ; left arrow
  102.  
  103.   ;; Key bindings for entering Human moves.
  104.   (bind-local-key  "gomoku-human-plays"        "X")    ; X
  105.   (bind-local-key  "gomoku-human-plays"        "x")    ; x
  106.   (bind-local-key  "gomoku-human-plays"        "C-m")    ; RET
  107. ; (bind-local-key  "gomoku-human-plays"        "C-Xp")    ; C-C P
  108.   (bind-local-key  "gomoku-human-resigns"    "C-Xr")    ; C-C R
  109.   (bind-local-key  "gomoku-emacs-plays"        "C-Xe")    ; C-C E
  110. ; (bind-local-key  "gomoku-human-takes-back"    "C-cb")    ; C-C B
  111. })
  112.  
  113.  
  114. ;;    Major mode for playing Gomoku against Emacs.  You and Emacs play in
  115. ;; turn by marking a free square.  You mark it with X and Emacs marks it
  116. ;; with O.  The winner is the first to get five contiguous marks
  117. ;; horizontally, vertically or in diagonal.  You play by moving the cursor
  118. ;; over the square you choose and hitting RET, x, ..  or whatever has been
  119. ;; set locally.
  120.  
  121. ;; Other useful commands:
  122. ;;   C-c r Indicate that you resign.
  123. ;;   C-c t Take back your last move.
  124. ;;   C-c e Ask for Emacs to play (thus passing).
  125.  
  126. (defun gomoku-mode
  127. {
  128. ;  (setq major-mode 'gomoku-mode    mode-name "Gomoku")
  129.   (gomoku-display-statistics)
  130.   (create-gomoku-mode-map)
  131. })
  132.  
  133. ;;;
  134. ;;; THE BOARD.
  135. ;;;
  136.  
  137. ;;   The board is a rectangular grid.  We code empty squares with 0, X's
  138. ;; with 1 and O's with 6.  The rectangle is recorded in a one dimensional
  139. ;; vector containing padding squares (coded with -1).  These squares allow
  140. ;; us to detect when we are trying to move out of the board.  We denote a
  141. ;; square by its (X,Y) coords, or by the INDEX corresponding to them in the
  142. ;; vector.  The leftmost topmost square has coords (1,1) and index
  143. ;; gomoku-board-width + 2.  Similarly, vectors between squares may be given
  144. ;; by two DX, DY coords or by one DEPL (the difference between indexes).
  145.  
  146. (const gomoku-max-vector-length 4000)
  147.  
  148.   ;; Number of columns on the Gomoku board.
  149. (int gomoku-board-width)
  150.  
  151.   ;; Number of lines on the Gomoku board.
  152. (int gomoku-board-height)
  153.  
  154.   ;; Vector recording the actual state of the Gomoku board.
  155. (array int gomoku-board gomoku-max-vector-length)
  156.  
  157.   ;; Length of gomoku-board vector.
  158. (int gomoku-vector-length)
  159.  
  160.   ;; After how many moves will Emacs offer a draw ?
  161.   ;; This is usually set to 70% of the number of squares.
  162. (int gomoku-draw-limit)
  163.  
  164.   ;; Translate X, Y cartesian coords into the corresponding board index.
  165. (defun gomoku-xy-to-index (int x y) { (+ (* y gomoku-board-width) x y) })
  166.  
  167.   ;; Return corresponding x-coord of board INDEX.
  168. (defun gomoku-index-to-x (int index) { (mod index (+ 1 gomoku-board-width)) })
  169.  
  170.   ;; Return corresponding y-coord of board INDEX.
  171. (defun gomoku-index-to-y (int index) { (/ index (+ 1 gomoku-board-width)) })
  172.  
  173.   ;; Create the gomoku-board vector and fill it with initial values.
  174. (defun gomoku-init-board
  175. {
  176.   (int i ii)
  177.  
  178. ;(setq gomoku-board (make-vector gomoku-vector-length 0))
  179.     ;; Every square is 0 (i.e. empty) except padding squares:
  180.  
  181.   (i gomoku-vector-length) (while (!= 0 (-= i 1)) (gomoku-board i 0))
  182.  
  183.   (i 0) (ii (- gomoku-vector-length 1))
  184.   (while (<= i gomoku-board-width)    ; The squares in [0..width] and in
  185.   {
  186.     (gomoku-board i  -1)        ;    [length - width - 1..length - 1]
  187.     (gomoku-board ii -1)        ;    are padding squares.
  188.     (+= i 1)(-= ii 1)
  189.   })
  190.  
  191.   (i 0)
  192.   (while (< i gomoku-vector-length)
  193.   {
  194.     (gomoku-board i -1)        ; and also all k*(width+1)
  195.     (+= i gomoku-board-width 1)
  196.   })
  197. })
  198.  
  199. ;;;
  200. ;;; THE SCORE TABLE.
  201. ;;;
  202.  
  203. ;; Every (free) square has a score associated to it, recorded in the
  204. ;; GOMOKU-SCORE-TABLE vector. The program always plays in the square having
  205. ;; the highest score.
  206.  
  207.   ;; Vector recording the actual score of the free squares.
  208. (array INT gomoku-score-table gomoku-max-vector-length)
  209.  
  210.  
  211. ;; The key point about the algorithm is that, rather than considering
  212. ;; the board as just a set of squares, we prefer to see it as a "space" of
  213. ;; internested 5-tuples of contiguous squares (called qtuples).
  214. ;;
  215. ;; The aim of the program is to fill one qtuple with its O's while preventing
  216. ;; you from filling another one with your X's. To that effect, it computes a
  217. ;; score for every qtuple, with better qtuples having better scores. Of
  218. ;; course, the score of a qtuple (taken in isolation) is just determined by
  219. ;; its contents as a set, i.e. not considering the order of its elements. The
  220. ;; highest score is given to the "OOOO" qtuples because playing in such a
  221. ;; qtuple is winning the game. Just after this comes the "XXXX" qtuple because
  222. ;; not playing in it is just loosing the game, and so on. Note that a
  223. ;; "polluted" qtuple, i.e. one containing at least one X and at least one O,
  224. ;; has score zero because there is no more any point in playing in it, from
  225. ;; both an attacking and a defending point of view.
  226. ;;
  227. ;; Given the score of every qtuple, the score of a given free square on the
  228. ;; board is just the sum of the scores of all the qtuples to which it belongs,
  229. ;; because playing in that square is playing in all its containing qtuples at
  230. ;; once. And it is that function which takes into account the internesting of
  231. ;; the qtuples.
  232. ;;
  233. ;; This algorithm is rather simple but anyway it gives a not so dumb level of
  234. ;; play. It easily extends to "n-dimensional Gomoku", where a win should not
  235. ;; be obtained with as few as 5 contiguous marks: 6 or 7 (depending on n !)
  236. ;; should be preferred.
  237.  
  238.  
  239. ;; Here are the scores of the nine "non-polluted" configurations.  Tuning
  240. ;; these values will change (hopefully improve) the strength of the program
  241. ;; and may change its style (rather aggressive here).
  242.  
  243. (const nil-score      7)    ; Score of an empty qtuple.
  244. (const Xscore         15)    ; Score of a qtuple containing one X.
  245. (const XXscore        400)    ; Score of a qtuple containing two X's.
  246. (const XXXscore           1800)    ; Score of a qtuple containing three X's.
  247. (const XXXXscore     100000)    ; Score of a qtuple containing four X's.
  248. (const Oscore         35)    ; Score of a qtuple containing one O.
  249. (const OOscore        800)    ; Score of a qtuple containing two O's.
  250. (const OOOscore          15000)    ; Score of a qtuple containing three O's.
  251. (const OOOOscore     800000)    ; Score of a qtuple containing four O's.
  252.  
  253. ;; These values are not just random: if, given the following situation:
  254. ;;
  255. ;;              . . . . . . . O .
  256. ;;              . X X a . . . X .
  257. ;;              . . . X . . . X .
  258. ;;              . . . X . . . X .
  259. ;;              . . . . . . . b .
  260. ;;
  261. ;; you want Emacs to play in "a" and not in "b", then the parameters must
  262. ;; satisfy the inequality:
  263. ;;
  264. ;;           6 * XXscore > XXXscore + XXscore
  265. ;;
  266. ;; because "a" mainly belongs to six "XX" qtuples (the others are less
  267. ;; important) while "b" belongs to one "XXX" and one "XX" qtuples.  Other
  268. ;; conditions are required to obtain sensible moves, but the previous example
  269. ;; should illustrate the point. If you manage to improve on these values,
  270. ;; please send me a note. Thanks.
  271.  
  272.  
  273. ;; As we choosed values 0, 1 and 6 to denote empty, X and O squares, the
  274. ;; contents of a qtuple is uniquely determined by the sum of its elements and
  275. ;; we just have to set up a translation table.
  276.  
  277. ;(defconst gomoku-score-trans-table
  278. ;  (vector nil-score Xscore XXscore XXXscore XXXXscore 0
  279. ;      Oscore    0       0       0        0          0
  280. ;      OOscore   0       0       0        0          0
  281. ;      OOOscore  0       0       0        0          0
  282. ;      OOOOscore 0       0       0        0          0
  283. ;      0)
  284.  
  285.   ;; Vector associating qtuple contents to their score.
  286. (array INT gomoku-score-trans-table 31)
  287. (defun gomoku-init-score-trans-table
  288. {
  289.   (gomoku-score-trans-table 0  nil-score)
  290.   (gomoku-score-trans-table 1  Xscore)
  291.   (gomoku-score-trans-table 2  XXscore)
  292.   (gomoku-score-trans-table 3  XXXscore)
  293.   (gomoku-score-trans-table 4  XXXXscore)
  294.   (gomoku-score-trans-table 6  Oscore)
  295.   (gomoku-score-trans-table 12 OOscore)
  296.   (gomoku-score-trans-table 18 OOOscore)
  297.   (gomoku-score-trans-table 24 OOOOscore)
  298. })
  299.  
  300. ;; If you do not modify drastically the previous constants, the only way for a
  301. ;; square to have a score higher than OOOOscore is to belong to a "OOOO"
  302. ;; qtuple, thus to be a winning move. Similarly, the only way for a square to
  303. ;; have a score between XXXXscore and OOOOscore is to belong to a "XXXX"
  304. ;; qtuple. We may use these considerations to detect when a given move is
  305. ;; winning or loosing.
  306.  
  307.   ;; Threshold score beyond which an emacs move is winning.
  308. (const gomoku-winning-threshold OOOOscore)
  309.  
  310.   ;; Threshold score beyond which a human move is winning.
  311. (const gomoku-loosing-threshold XXXXscore)
  312.  
  313.   ;; Compute index of free square with highest score, or nil if none.
  314. (defun gomoku-strongest-square
  315. {
  316.   ;; We just have to loop other all squares. However there are two problems:
  317.   ;; 1/ The SCORE-TABLE only gives correct scores to free squares. To speed
  318.   ;;    up future searches, we set the score of padding or occupied squares
  319.   ;;    to -1 whenever we meet them.
  320.   ;; 2/ We want to choose randomly between equally good moves.
  321.  
  322.   (INT score score-max)
  323.   (int count square end best-square)
  324.  
  325.   (score-max 0)
  326.   (count   0)                ; Number of equally good moves
  327.   (square  (gomoku-xy-to-index 1 1))    ; First square
  328.   (end       (gomoku-xy-to-index gomoku-board-width gomoku-board-height))
  329.   (while (<= square end)
  330.   {
  331.     (case
  332.     ;; If score is lower (i.e. most of the time), skip to next:
  333.       (< (gomoku-score-table square) score-max) ()
  334.     ;; If score is better, beware of non free squares:
  335.       (> (score (gomoku-score-table square)) score-max)
  336.         (if (== 0 (gomoku-board square))    ; is it free ?
  337.       {
  338.         (count 1)                ; yes: take it !
  339.         (best-square square)
  340.         (score-max   score)
  341.       }
  342.       (gomoku-score-table square -1)    ; no: kill it !
  343.     )
  344.     ;; If score is equally good, choose randomly. But first check freeness:
  345.       (!= 0 (gomoku-board square)) (gomoku-score-table square -1)
  346.       (== count (random-number (+= count 1)))
  347.     { (best-square square)(score-max score) }
  348.     )
  349.     (+= square 1)    ; try next square
  350.   })
  351.   best-square
  352. })
  353.  
  354.   ;; Return a random integer between 0 and N-1 inclusive.
  355. (defun random-number (n) { (mod (rand) n) })
  356.  
  357. ;;;
  358. ;;; INITIALIZING THE SCORE TABLE.
  359. ;;;
  360.  
  361. ;; At initialization the board is empty so that every qtuple amounts for
  362. ;; nil-score. Therefore, the score of any square is nil-score times the number
  363. ;; of qtuples that pass through it. This number is 3 in a corner and 20 if you
  364. ;; are sufficiently far from the sides. As computing the number is time
  365. ;; consuming, we initialize every square with 20*nil-score and then only
  366. ;; consider squares at less than 5 squares from one side. We speed this up by
  367. ;; taking symmetry into account.
  368. ;; Also, as it is likely that successive games will be played on a board with
  369. ;; same size, it is a good idea to save the initial SCORE-TABLE configuration.
  370.  
  371.   ;; Recorded initial value of previous score table.
  372. ;(??? gomoku-saved-score-table)
  373.  
  374.   ;; Recorded value of previous board width.
  375. (int gomoku-saved-board-width)
  376.  
  377.   ;; Recorded value of previous board height.
  378. (int gomoku-saved-board-height)
  379.  
  380.  
  381.   ;; Create the score table vector and fill it with initial values.
  382. (defun gomoku-init-score-table
  383. {
  384.   (int i j maxi maxj maxi2 maxj2)
  385.  
  386. ;  (if (and gomoku-saved-score-table    ; Has it been stored last time ?
  387. ;       (= gomoku-board-width  gomoku-saved-board-width)
  388. ;       (= gomoku-board-height gomoku-saved-board-height))
  389. ;      (setq gomoku-score-table (copy-sequence gomoku-saved-score-table))
  390.       ;; No, compute it:
  391.  
  392. ;
  393. ;(setq gomoku-score-table
  394. ;        (make-vector gomoku-vector-length (* 20 nil-score)))
  395.   (i 0)
  396.   (while (< i gomoku-vector-length)
  397.     { (gomoku-score-table i (* 20 nil-score)) (+= i 1) })
  398.  
  399.   (maxi  (/ (+ 1 gomoku-board-width) 2))
  400.   (maxj  (/ (+ 1 gomoku-board-height) 2))
  401.   (maxi2 (min 4 maxi))
  402.   (maxj2 (min 4 maxj))
  403.     ;; We took symmetry into account and could use it more if the board
  404.     ;; would have been square and not rectangular !
  405.     ;; In our case we deal with all (i,j) in the set [1..maxi2]*[1..maxj] U
  406.     ;; [maxi2+1..maxi]*[1..maxj2]. Maxi2 and maxj2 are used because the
  407.     ;; board may well be less than 8 by 8 !
  408.   (i 1)
  409.   (while (<= i maxi2)
  410.   {
  411.     (j 1)
  412.     (while (<= j maxj) { (gomoku-init-square-score i j) (+= j 1) })
  413.     (+= i 1)
  414.   })
  415.   (while (<= i maxi)
  416.   {
  417.     (j 1)
  418.     (while (<= j maxj2) { (gomoku-init-square-score i j) (+= j 1) })
  419.     (+= i 1)
  420.   })
  421. ;(setq gomoku-saved-score-table  (copy-sequence gomoku-score-table)
  422. ;        gomoku-saved-board-width  gomoku-board-width
  423. ;        gomoku-saved-board-height gomoku-board-height)
  424. })
  425.  
  426.   ;; Return the number of qtuples containing square I,J.
  427. (defun gomoku-nb-qtuples (int i j)
  428. {
  429.   ;; This function is complicated because we have to deal
  430.   ;; with ugly cases like 3 by 6 boards, but it works.
  431.   ;; If you have a simpler (and correct) solution, send it to me. Thanks !
  432.  
  433.   (int left right up down)
  434.  
  435.   (left  (min 4 (- i 1)))
  436.   (right (min 4 (- gomoku-board-width i)))
  437.   (up    (min 4 (- j 1)))
  438.   (down  (min 4 (- gomoku-board-height j)))
  439.   (+ -12
  440.      (min (max (+ left right) 3) 8)
  441.      (min (max (+ up down) 3) 8)
  442.      (min (max (+ (min left up) (min right down)) 3) 8)
  443.      (min (max (+ (min right up) (min left down)) 3) 8))
  444. })
  445.  
  446.   ;; Give initial score to square I,J and to its mirror images.
  447. (defun gomoku-init-square-score (int i j)
  448. {
  449.   (int ii jj)(INT sc)
  450.  
  451.   (ii (+ 1 (- gomoku-board-width i)))
  452.   (jj (+ 1 (- gomoku-board-height j)))
  453.   (sc (* (gomoku-nb-qtuples i j) (gomoku-score-trans-table 0)))
  454.   (gomoku-score-table (gomoku-xy-to-index i  j)  sc)
  455.   (gomoku-score-table (gomoku-xy-to-index ii j)     sc)
  456.   (gomoku-score-table (gomoku-xy-to-index i  jj) sc)
  457.   (gomoku-score-table (gomoku-xy-to-index ii jj) sc)
  458. })
  459.  
  460. ;;;
  461. ;;; MAINTAINING THE SCORE TABLE.
  462. ;;;
  463.  
  464. ;; We do not provide functions for computing the SCORE-TABLE given the
  465. ;; contents of the BOARD. This would involve heavy nested loops, with time
  466. ;; proportional to the size of the board. It is better to update the
  467. ;; SCORE-TABLE after each move. Updating needs not modify more than 36
  468. ;; squares: it is done in constant time.
  469.  
  470.   ;; Update score table after SQUARE received a DVAL increment.
  471. (defun gomoku-update-score-table (int square dval)
  472. {
  473.   ;; The board has already been updated when this function is called.
  474.   ;; Updating scores is done by looking for qtuples boundaries in all four
  475.   ;; directions and then calling update-score-in-direction.
  476.   ;; Finally all squares received the right increment, and then are up to
  477.   ;; date, except possibly for SQUARE itself if we are taking a move back for
  478.   ;; its score had been set to -1 at the time.
  479.  
  480.   (int x y imin jmin imax jmax)
  481.  
  482.   (x (gomoku-index-to-x square))
  483.   (y (gomoku-index-to-y square))
  484.   (imin (max -4 (- 1 x)))
  485.   (jmin (max -4 (- 1 y)))
  486.   (imax (min 0 (- gomoku-board-width x 4)))
  487.   (jmax (min 0 (- gomoku-board-height y 4)))
  488.   (gomoku-update-score-in-direction imin imax square 1 0 dval)
  489.   (gomoku-update-score-in-direction jmin jmax square 0 1 dval)
  490.   (gomoku-update-score-in-direction
  491.     (max imin jmin) (min imax jmax) square 1 1 dval)
  492.   (gomoku-update-score-in-direction
  493.     (max (- 1 y) -4 (- x gomoku-board-width))
  494.     (min 0 (- x 5) (- gomoku-board-height y 4))
  495.     square -1 1 dval)
  496. })
  497.  
  498.   ;; Update scores for all squares in the qtuples starting between the
  499.   ;;   LEFTth square and the RIGHTth after SQUARE, along the DX, DY
  500.   ;;   direction, considering that DVAL has been added on SQUARE.
  501. (defun gomoku-update-score-in-direction (int left right sq dx dy dval)
  502. {
  503.   ;; We always have LEFT <= 0, RIGHT <= 0 and DEPL > 0 but we may very well
  504.   ;; have LEFT > RIGHT, indicating that no qtuple contains SQUARE along that
  505.   ;; DX,DY direction.
  506.  
  507.   (int depl square square0 square1 square2 count)
  508.   (INT delta)
  509.  
  510.   (square sq)
  511.   (if (> left right) (done))        ; Quit
  512.   (depl    (gomoku-xy-to-index dx dy))
  513.   (square0 (+ square (* left depl)))
  514.   (square1 (+ square (* right depl)))
  515.   (square2 (+ square0 (* 4 depl)))
  516.       ;; Compute the contents of the first qtuple:
  517.   (square square0)
  518.   (count  0)
  519.   (while (<= square square2)
  520.     { (+= count (gomoku-board square)) (+= square depl) })
  521.   (while (<= square0 square1)
  522.   {
  523.     ;; Update the squares of the qtuple beginning in SQUARE0 and ending
  524.     ;; in SQUARE2.
  525.     (delta (- (gomoku-score-trans-table count)
  526.           (gomoku-score-trans-table (- count dval))))
  527.     (if (!= 0 delta)        ; or else nothing to update
  528.     {
  529.       (square square0)
  530.       (while (<= square square2)
  531.       {
  532.     (if (== 0 (gomoku-board square))     ; only for free squares
  533.       (gomoku-score-table square (+ (gomoku-score-table square) delta)))
  534.     (+= square depl)
  535.       })
  536.     })
  537.     ;; Then shift the qtuple one square along DEPL, this only requires
  538.     ;; modifying SQUARE0 and SQUARE2.
  539.     (+= square2 depl)
  540.     (+= count (- (gomoku-board square2) (gomoku-board square0)) )
  541.     (+= square0 depl)
  542.   })
  543. })
  544.  
  545. ;;;
  546. ;;; GAME CONTROL.
  547. ;;;
  548.  
  549. ;; Several variables are used to monitor a game, including a GAME-HISTORY (the
  550. ;; list of all (SQUARE . PREVSCORE) played) that allows to take moves back
  551. ;; (anti-updating the score table) and to compute the table from scratch in
  552. ;; case of an interruption.
  553.  
  554.   ;; Non-nil if a game is in progress.
  555. (bool gomoku-game-in-progress)
  556.  
  557.   ;; Number of moves already played in current game.
  558. (int gomoku-number-of-moves)
  559.  
  560.   ;; Number of moves already played by human in current game.
  561. (int gomoku-number-of-human-moves)
  562.  
  563.   ;; Non-nil if Emacs played first.
  564. (bool gomoku-emacs-played-first)
  565.  
  566.   ;; Non-nil if Human took back a move during the game.
  567. (bool gomoku-human-took-back)
  568.  
  569.   ;; Non-nil if Human refused Emacs offer of a draw.
  570. (bool gomoku-human-refused-draw)
  571.  
  572.   ;; This is used to detect interruptions. Hopefully, it should not be needed.
  573.   ;; Non-nil if Emacs is in the middle of a computation.
  574. (bool gomoku-emacs-is-computing)
  575.  
  576.  
  577.   ;; Initialize a new game on an N by M board.
  578. (defun gomoku-start-game (int n m)
  579. {
  580.   (gomoku-emacs-is-computing TRUE)    ; Raise flag
  581.   (gomoku-game-in-progress TRUE)
  582.   (gomoku-board-width  n) (gomoku-board-height m)
  583.   (gomoku-vector-length (+ 1 (* (+ m 2) (+ 1 n))))
  584. (if (<= gomoku-max-vector-length gomoku-vector-length)
  585. (error "Board too big"))
  586.   (gomoku-draw-limit (/ (* 7 n m) 10))
  587.   (gomoku-number-of-moves 0)
  588.   (gomoku-number-of-human-moves 0)
  589.   (gomoku-emacs-played-first TRUE)
  590.   (gomoku-human-took-back    FALSE)
  591.   (gomoku-human-refused-draw FALSE)
  592.   (gomoku-init-display n m)        ; Display first: the rest takes time
  593.   (gomoku-init-score-trans-table)
  594.   (gomoku-init-score-table)        ; INIT-BOARD requires that the score
  595.   (gomoku-init-board)            ;   table be already created.
  596.   (gomoku-emacs-is-computing FALSE)
  597. })
  598.  
  599.   ;; Go to SQUARE, play VAL and update everything.
  600. (defun gomoku-play-move (int square val) ; &optional dont-update-score
  601. {
  602.   (gomoku-emacs-is-computing TRUE)    ; Raise flag
  603.   (case
  604.     (== 1 val)            ; a Human move
  605.     (gomoku-number-of-human-moves (+ 1 gomoku-number-of-human-moves))
  606.     (== 0 gomoku-number-of-moves)    ; an Emacs move. Is it first ?
  607.     (gomoku-emacs-played-first TRUE)
  608.   )
  609. ;  (setq gomoku-game-history
  610. ;    (cons (cons square (aref gomoku-score-table square))
  611. ;          gomoku-game-history)
  612.  
  613.   (+= gomoku-number-of-moves 1)
  614.  
  615.   (gomoku-plot-square square val)
  616.   (gomoku-board square val)    ; *BEFORE* UPDATE-SCORE !
  617.   (gomoku-update-score-table square val) ; previous val was 0: dval = val
  618.   (gomoku-score-table square -1)
  619.   (gomoku-emacs-is-computing FALSE)
  620. })
  621.  
  622.   ;; Take back last move and update everything.
  623. (defun gomoku-take-back
  624. {
  625. ;  (setq gomoku-emacs-is-computing t)
  626. ;  (let* ((last-move (car gomoku-game-history))
  627. ;     (square (car last-move))
  628. ;     (oldval (aref gomoku-board square)))
  629. ;    (if (= 1 oldval)
  630. ;    (setq gomoku-number-of-human-moves (1- gomoku-number-of-human-moves)))
  631. ;    (setq gomoku-game-history     (cdr gomoku-game-history)
  632. ;      gomoku-number-of-moves (1- gomoku-number-of-moves))
  633. ;    (gomoku-plot-square square 0)
  634. ;    (aset gomoku-board square 0)    ; *BEFORE* UPDATE-SCORE !
  635. ;    (gomoku-update-score-table square (- oldval))
  636. ;    (aset gomoku-score-table square (cdr last-move)))
  637. ;  (setq gomoku-emacs-is-computing nil))
  638. })
  639.  
  640. ;;;
  641. ;;; SESSION CONTROL.
  642. ;;;
  643.  
  644.   ;; Number of games already won in this session.
  645. (int gomoku-number-of-wins)
  646.  
  647.   ;; Number of games already lost in this session.
  648. (int gomoku-number-of-losses)
  649.  
  650.   ;; Number of games already drawn in this session.
  651. (int gomoku-number-of-draws)
  652.  
  653.  
  654. (const
  655.   emacs-won     1
  656.   human-won     2
  657.   nobody-won     3
  658.   draw-agreed     4
  659.   human-resigned 5
  660.   crash-game     6
  661. )
  662.  
  663.   ;; Terminate the current game with RESULT.
  664. (defun gomoku-terminate-game (int result)
  665. {
  666.   (string message 80)
  667.  
  668.   (switch result
  669.     emacs-won
  670.     {
  671.       (gomoku-number-of-wins (+ 1 gomoku-number-of-wins))
  672.       (message
  673.         (case
  674.       (< gomoku-number-of-moves 20) "This was a REALLY QUICK win."
  675.       gomoku-human-refused-draw
  676.         "I won... Too bad you refused my offer of a draw !"
  677.       gomoku-human-took-back
  678.         "I won... Taking moves back will not help you !"
  679.       (not gomoku-emacs-played-first)
  680.         "I won... Playing first did not help you much !"
  681.       (and (== 0 gomoku-number-of-losses)
  682.            (== 0 gomoku-number-of-draws)
  683.            (> gomoku-number-of-wins 1))
  684.            "I'm becoming tired of winning..."
  685.       TRUE "I won."
  686.     )
  687.       )
  688.     }
  689.     human-won
  690.     {
  691.       (gomoku-number-of-losses (+ 1 gomoku-number-of-losses))
  692.       (message
  693.         (case
  694.       gomoku-human-took-back
  695.         "OK, you won this one. I, for one, never take my moves back..."
  696.       gomoku-emacs-played-first "OK, you won this one... so what ?"
  697.       TRUE
  698.         "OK, you won this one. Now, let me play first just once."
  699.     )
  700.       )
  701.     }
  702.     human-resigned
  703.     {
  704.       (gomoku-number-of-wins (+ 1 gomoku-number-of-wins))
  705.       (message "So you resign... That's just one more win for me.")
  706.     }
  707.     nobody-won
  708.     {
  709.       (gomoku-number-of-draws (+ 1 gomoku-number-of-draws))
  710.       (message
  711.         (case
  712.       gomoku-human-took-back
  713.         "This is a draw. I, for one, never take my moves back..."
  714.       gomoku-emacs-played-first "This is a draw... Just chance, I guess."
  715.       TRUE "This is a draw. Now, let me play first just once."
  716.     )
  717.       )
  718.     }
  719.     draw-agreed
  720.     {
  721.       (gomoku-number-of-draws (+ 1 gomoku-number-of-draws))
  722.       (message
  723.         (case
  724.       gomoku-human-took-back
  725.         "Draw agreed. I, for one, never take my moves back..."
  726.       gomoku-emacs-played-first "Draw agreed. You were lucky."
  727.       TRUE "Draw agreed. Now, let me play first just once."
  728.     )
  729.       )
  730.     }
  731.     crash-game
  732.       (message "Sorry, I have been interrupted and cannot resume that game...")
  733.   )
  734.   (gomoku-display-statistics)
  735.   (msg message)
  736.   (gomoku-game-in-progress FALSE)
  737. })
  738.  
  739.   ;; What to do when Emacs detects it has been interrupted.
  740. (defun gomoku-crash-game
  741. {
  742.   (gomoku-emacs-is-computing FALSE)
  743.   (gomoku-terminate-game crash-game)
  744. ;  (sit-for 4)                ; Let's see the message
  745.   (gomoku-prompt-for-other-game)
  746. })
  747.  
  748. ;;;
  749. ;;; INTERACTIVE COMMANDS.
  750. ;;;
  751.  
  752. (defun error (string error-message)
  753. {
  754.   (msg error-message)(halt)
  755. })
  756.  
  757. ;; Start a Gomoku game between you and Emacs.
  758. ;; If a game is in progress, this command allows you to resume it.
  759. ;; If optional arguments N and M are given, an N by M board is used.
  760. ;; You and Emacs play in turn by marking a free square.  You mark it with X
  761. ;;   and Emacs marks it with O.  The winner is the first to get five
  762. ;;   contiguous marks horizontally, vertically or in diagonal.
  763. ;; You play by moving the cursor over the square you choose and hitting RET,
  764. ;;   x, ..  or whatever has been set locally.
  765. (defun gomoku
  766. {
  767.   (int n m max-width max-height)
  768.  
  769.   (n 0)(m 0)
  770.   (if (arg-flag)
  771.     {
  772.       (n (atoi (ask "Gomoku board width: ")))
  773.       (m (atoi (ask "Gomoku board height: ")))
  774.     }
  775.     (if (!= 0 (nargs)) { (n (arg 0)) (m (arg 1)) })
  776.   )
  777.  
  778.   (gomoku-switch-to-window)
  779.  
  780.   (case
  781.     gomoku-emacs-is-computing (gomoku-crash-game) ; ???
  782.     (not gomoku-game-in-progress)
  783.     {
  784.       (max-width (gomoku-max-width)) (max-height (gomoku-max-height))
  785.       (if (== 0 n) (n max-width))
  786.       (if (== 0 m) (m max-height))
  787.       (case
  788.         (< n 1) (error "I need at least 1 column")
  789.     (< m 1) (error "I need at least 1 row")
  790.     (> n max-width)
  791.       (error (concat "I cannot display " n " columns in that window"))
  792.       )
  793.       (if (and (> m max-height)
  794.            (!= m gomoku-saved-board-height)
  795.            (not (yesno "Do you really want " m " rows")))
  796.       (m max-height))
  797.       (msg "One moment, please...")
  798.       (gomoku-start-game n m)
  799.       (if (yesno "Do you allow me to play first")
  800.     (gomoku-emacs-plays)
  801.     (gomoku-prompt-for-move))
  802.     }
  803.     (yesno "Shall we continue our game") (gomoku-prompt-for-move)
  804.     TRUE (gomoku-human-resigns)
  805.   )
  806. })
  807.  
  808.   ;; Compute Emacs next move and play it.
  809. (defun gomoku-emacs-plays
  810. {
  811.   (int square) (INT score)
  812.  
  813. ;  (gomoku-switch-to-window)
  814.   (case
  815.     gomoku-emacs-is-computing (gomoku-crash-game)
  816.     (not gomoku-game-in-progress) (gomoku-prompt-for-other-game)
  817.     TRUE
  818.     {
  819.       (msg "Let me think...")
  820.       (square (gomoku-strongest-square))
  821.       (case
  822.         (== 0 square) (gomoku-terminate-game nobody-won)
  823.     TRUE
  824.     {
  825.       (score (gomoku-score-table square))
  826.       (gomoku-play-move square 6)
  827.       (case
  828.         (>= score gomoku-winning-threshold)
  829.         {
  830.           (gomoku-find-filled-qtuple square 6)
  831.           (gomoku-cross-winning-qtuple)
  832.           (gomoku-terminate-game emacs-won)
  833.         }
  834.         (== 0 score) (gomoku-terminate-game nobody-won)
  835.         (and (> gomoku-number-of-moves gomoku-draw-limit)
  836.          (not gomoku-human-refused-draw)
  837.          (gomoku-offer-a-draw))
  838.            (gomoku-terminate-game draw-agreed)
  839.         TRUE (gomoku-prompt-for-move)
  840.       )
  841.     }
  842.       )
  843.     }
  844.   )
  845. })
  846.  
  847.   ;; Signal to the Gomoku program that you have played.
  848.   ;; You must have put the cursor on the square where you want to play.
  849.   ;; If the game is finished, this command requests for another game.
  850. (defun gomoku-human-plays
  851. {
  852.   (int square) (INT score)
  853.  
  854.   (gomoku-switch-to-window)
  855.   (case
  856.     gomoku-emacs-is-computing (gomoku-crash-game)
  857.     (not gomoku-game-in-progress) (gomoku-prompt-for-other-game)
  858.     TRUE
  859.     {
  860.       (square (gomoku-point-square))
  861.       (case
  862.         (== 0 square) (error "Your point is not on a square. Retry !")
  863.     (!= 0 (gomoku-board square))
  864.       (error "Your point is not on a free square. Retry !")
  865.     TRUE
  866.     {
  867.       (score (gomoku-score-table square))
  868.       (gomoku-play-move square 1)
  869.       (case
  870.         (and (>= score gomoku-loosing-threshold)
  871.             ;; Just testing SCORE > THRESHOLD is not enough for
  872.             ;; detecting wins, it just gives an indication that
  873.             ;; we confirm with GOMOKU-FIND-FILLED-QTUPLE.
  874.          (gomoku-find-filled-qtuple square 1))
  875.           {
  876.             (gomoku-cross-winning-qtuple)
  877.             (gomoku-terminate-game human-won)
  878.           }
  879.         TRUE (gomoku-emacs-plays)
  880.       )
  881.     }
  882.       )
  883.     }
  884.   )
  885. })
  886.  
  887.   ;; Signal to the Gomoku program that you wish to take back your last move.
  888. (defun gomoku-human-takes-back
  889. {
  890. (msg "Take back not implemented yet")
  891. ;  (gomoku-switch-to-window)
  892. ;  (cond
  893. ;   (gomoku-emacs-is-computing
  894. ;    (gomoku-crash-game))
  895. ;   ((not gomoku-game-in-progress)
  896. ;    (message "Too late for taking back...")
  897. ;    (sit-for 4)
  898. ;    (gomoku-prompt-for-other-game))
  899. ;   ((zerop gomoku-number-of-human-moves)
  900. ;    (message "You have not played yet... Your move ?"))
  901. ;   (t
  902. ;    (message "One moment, please...")
  903.     ;; It is possible for the user to let Emacs play several consecutive
  904.     ;; moves, so that the best way to know when to stop taking back moves is
  905.     ;; to count the number of human moves:
  906. ;    (setq gomoku-human-took-back t)
  907. ;    (let ((number gomoku-number-of-human-moves))
  908. ;      (while (= number gomoku-number-of-human-moves)
  909. ;    (gomoku-take-back)))
  910. ;    (gomoku-prompt-for-move))))
  911. })
  912.  
  913.   ;; Signal to the Gomoku program that you may want to resign.
  914. (defun gomoku-human-resigns
  915. {
  916.   (gomoku-switch-to-window)
  917.   (case
  918.     gomoku-emacs-is-computing (gomoku-crash-game)
  919.     (not gomoku-game-in-progress) (msg "There is no game in progress")
  920.     (yesno "You mean, you resign") (gomoku-terminate-game human-resigned)
  921.     (yesno "You mean, we continue") (gomoku-prompt-for-move)
  922.     TRUE (gomoku-terminate-game human-resigned)    ; OK. Accept it
  923.   )
  924. })
  925.  
  926. ;;;
  927. ;;; PROMPTING THE HUMAN PLAYER.
  928. ;;;
  929.  
  930.   ;; Display a message asking for Human's move.
  931. (defun gomoku-prompt-for-move
  932. {
  933.   (msg
  934.     (if (== 0 gomoku-number-of-human-moves)
  935.     "Your move ? (move to a free square and hit X, RET ...)"
  936.     "Your move ?"))
  937.   ;; This may seem silly, but if one omits the following line (or a similar
  938.   ;; one), the cursor may very well go to some place where POINT is not.
  939. ;???  (save-excursion (set-buffer (other-buffer))))
  940. })
  941.  
  942.   ;; Ask for another game, and start it.
  943. (defun gomoku-prompt-for-other-game
  944. {
  945.   (if (yesno "Another game")
  946.     (gomoku gomoku-board-width gomoku-board-height)
  947.     (msg "Chicken !"))
  948. })
  949.  
  950.   ;; Offer a draw and return T if Human accepted it.
  951. (defun gomoku-offer-a-draw
  952. {
  953.   (if (yesno "I offer you a draw. Do you accept it")
  954.     (gomoku-human-refused-draw TRUE)
  955.     FALSE)
  956. })
  957.  
  958. ;;;
  959. ;;; DISPLAYING THE BOARD.
  960. ;;;
  961.  
  962. ;; You may change these values if you have a small screen or if the squares
  963. ;; look rectangular, but spacings SHOULD be at least 2 (MUST BE at least 1).
  964.  
  965.   ;; Horizontal spacing between squares on the Gomoku board.
  966. (const gomoku-square-width 4)
  967.  
  968.   ;; Vertical spacing between squares on the Gomoku board.
  969. (const gomoku-square-height 2)
  970.  
  971.   ;; Number of columns between the Gomoku board and the side of the window.
  972. (const gomoku-x-offset 3)
  973.  
  974.   ;; Number of lines between the Gomoku board and the top of the window.
  975. (const gomoku-y-offset 1)
  976.  
  977.  
  978.   ;; Largest possible board width for the current window.
  979. (defun gomoku-max-width
  980. {
  981.   (+ 1 (/ (- (screen-width) gomoku-x-offset gomoku-x-offset 1)
  982.      gomoku-square-width))
  983. })
  984.  
  985.   ;; Largest possible board height for the current window.
  986. (defun gomoku-max-height
  987. {
  988.   (+ 1 (/ (- (window-height -1) gomoku-y-offset gomoku-y-offset 1)
  989.      ;; 2 instead of 1 because WINDOW-HEIGHT includes the mode line !
  990.      gomoku-square-height))
  991. })
  992.  
  993.  ;; Return the board column where point is, or nil if it is not a board column.
  994. (defun gomoku-point-x
  995. {
  996.   (int col)
  997.  
  998.   (col (- (current-column) gomoku-x-offset 1))
  999.   (if (and (>= col 0)
  1000.        (== 0 (mod col gomoku-square-width))
  1001.        (<= (col (+ 1 (/ col gomoku-square-width))) gomoku-board-width))
  1002.     col
  1003.     0)
  1004. })
  1005.  
  1006.   ;; Return the board row where point is, or nil if it is not a board row.
  1007. (defun gomoku-point-y
  1008. {
  1009.   (int row)
  1010.   (INT buffer-size dot lines buffer-row wasted char-at-dot)
  1011.   
  1012.   (buffer-stats -1 (loc buffer-size))
  1013.  
  1014.   (row (- (buffer-row) gomoku-y-offset 1))
  1015.   (if (and (>= row 0)
  1016.       (== 0 (mod row gomoku-square-height))
  1017.       (<= (row (+ 1 (/ row gomoku-square-height))) gomoku-board-height))
  1018.     row
  1019.     0)
  1020. })
  1021.  
  1022.   ;; Return the index of the square point is on, or nil if not on the board.
  1023. (defun gomoku-point-square
  1024. {
  1025.   (int x y)
  1026.  
  1027.   (if (and (!= 0 (x (gomoku-point-x)))(!= 0 (y (gomoku-point-y))))
  1028.     (gomoku-xy-to-index x y)
  1029.     0)
  1030. })
  1031.  
  1032.   ;; Move point to square number INDEX.
  1033. (defun gomoku-goto-square (int index)
  1034.   { (gomoku-goto-xy (gomoku-index-to-x index) (gomoku-index-to-y index)) })
  1035.  
  1036.   ;; Move point to square at X, Y coords.
  1037. (defun gomoku-goto-xy (int x y)
  1038. {
  1039.   (goto-line (+ 1 gomoku-y-offset (* gomoku-square-height (- y 1))))
  1040.   (current-column (+ 1 gomoku-x-offset (* gomoku-square-width (- x 1))))
  1041. })
  1042.  
  1043.   ;; Draw 'X', 'O' or '.' on SQUARE (depending on VALUE), leave point there.
  1044. (defun gomoku-plot-square (int square value)
  1045. {
  1046.   (gomoku-goto-square square)
  1047.   (gomoku-put-char (case (== value 1) "X"
  1048.              (== value 6) "O"
  1049.              TRUE          "."))
  1050.   (update)    ; Display NOW
  1051. })
  1052.  
  1053.   ;; Draw CHAR on the Gomoku screen.
  1054. (defun gomoku-put-char (string char)
  1055. {
  1056.   (insert-text char)
  1057.   (delete-character)
  1058.   (previous-character)
  1059. })
  1060.  
  1061. (const BLANKS "          ")
  1062.  
  1063.   ;; Display an N by M Gomoku board.
  1064. (defun gomoku-init-display (int n m)
  1065. {
  1066.   (int i j)
  1067.   (string row 200)
  1068.  
  1069.   (clear-buffer -1)
  1070.     ;; We do not use gomoku-plot-square which would be too slow for
  1071.     ;; initializing the display. Rather we build STRING1 for lines where
  1072.     ;; board squares are to be found, and STRING2 for empty lines. STRING1 is
  1073.     ;; like STRING2 except for dots every DX squares. Empty lines are filled
  1074.     ;; with spaces so that cursor moving up and down remains on the same
  1075.     ;; column.
  1076.   (row (concat (substr BLANKS 0 gomoku-x-offset) "."))
  1077.   (j 0)(while (< (+= j 1) n)
  1078.     (row (concat row (substr BLANKS 0 (- gomoku-square-width 1)) ".")))
  1079.   (j 0)
  1080.   (arg-prefix gomoku-y-offset)(newline)
  1081.   (while (<= (+= j 1) m)
  1082.   {
  1083.     (insert-text row)
  1084.     (arg-prefix gomoku-square-height)(newline)
  1085.   })
  1086.   (beginning-of-buffer)
  1087.  
  1088.   (gomoku-goto-xy (/ (+ 1 n) 2) (/ (+ 1 m) 2)) ; center of the board
  1089.   (update)                ; Display NOW
  1090. })
  1091.  
  1092.   ;; Obnoxiously display some statistics about previous games in mode line.
  1093. (defun gomoku-display-statistics
  1094. {
  1095.   ;; We store this string in the mode-line-process local variable.
  1096.   ;; This is certainly not the cleanest way out ...
  1097. ;  (setq mode-line-process
  1098. ;    (cond
  1099. ;     ((not (zerop gomoku-number-of-draws))
  1100. ;      (format ": Won %d, lost %d, drew %d"
  1101. ;          gomoku-number-of-wins
  1102. ;          gomoku-number-of-losses
  1103. ;          gomoku-number-of-draws))
  1104. ;     ((not (zerop gomoku-number-of-losses))
  1105. ;      (format ": Won %d, lost %d"
  1106. ;          gomoku-number-of-wins
  1107. ;          gomoku-number-of-losses))
  1108. ;     ((zerop gomoku-number-of-wins)
  1109. ;      "")
  1110. ;     ((= 1 gomoku-number-of-wins)
  1111. ;      ": Already won one")
  1112. ;     (t
  1113. ;      (format ": Won %d in a row"
  1114. ;          gomoku-number-of-wins))))
  1115.   ;; Then a (standard) kludgy line will force update of mode line.
  1116. ;  (set-buffer-modified-p (buffer-modified-p)))
  1117. })
  1118.  
  1119.   ;; Find or create the Gomoku buffer, and display it.
  1120. (defun gomoku-switch-to-window
  1121. {
  1122.   (int b)
  1123.  
  1124.   (if (== (current-buffer) (b (attached-buffer "*Gomoku*"))) (done))
  1125.   (if (!= -1 b)
  1126.     {        ; Buffer exists: no problem.
  1127.       (switch-to-buffer "*Gomoku*")
  1128.     }
  1129.     {
  1130.       (if gomoku-game-in-progress
  1131.      (gomoku-crash-game))        ; Buffer has been killed or something
  1132.       (switch-to-buffer "*Gomoku*")    ; Anyway, start anew.
  1133.       (buffer-flags (attached-buffer "*Gomoku*") BFNoCare)
  1134.       (gomoku-mode)
  1135.     }
  1136.   )
  1137. ;  (arg-prefix 1000)(scroll-up)(update)
  1138. })
  1139.  
  1140. ;;;
  1141. ;;; CROSSING WINNING QTUPLES.
  1142. ;;;
  1143.  
  1144. ;; When someone succeeds in filling a qtuple, we draw a line over the five
  1145. ;; corresponding squares. One problem is that the program does not know which
  1146. ;; squares ! It only knows the square where the last move has been played and
  1147. ;; who won. The solution is to scan the board along all four directions.
  1148.  
  1149.   ;; First square of the winning qtuple.
  1150. (int gomoku-winning-qtuple-beg)
  1151.  
  1152.   ;; Last square of the winning qtuple.
  1153. (int gomoku-winning-qtuple-end)
  1154.  
  1155.   ;; Direction of the winning qtuple (along the X axis).
  1156. (int gomoku-winning-qtuple-dx)
  1157.  
  1158.   ;; Direction of the winning qtuple (along the Y axis).
  1159. (int gomoku-winning-qtuple-dy)
  1160.  
  1161.  
  1162.   ;; Return T if SQUARE belongs to a qtuple filled with VALUEs.
  1163. (defun gomoku-find-filled-qtuple (int square value)
  1164. {
  1165.   (or (gomoku-check-filled-qtuple square value 1 0)
  1166.       (gomoku-check-filled-qtuple square value 0 1)
  1167.       (gomoku-check-filled-qtuple square value 1 1)
  1168.       (gomoku-check-filled-qtuple square value -1 1))
  1169. })
  1170.  
  1171.   ;; Return T if SQUARE belongs to a qtuple filled  with VALUEs along DX, DY.
  1172.   ;; And record it in the WINNING-QTUPLE-... variables.
  1173. (defun gomoku-check-filled-qtuple (int square value dx dy)
  1174. {
  1175.   (int a b left right depl a+4)
  1176.  
  1177.   (a 0) (b 0)
  1178.   (left square) (right square)
  1179.   (depl (gomoku-xy-to-index dx dy))
  1180.   (while
  1181.     (and (> a -4)        ; stretch tuple left
  1182.      (== value (gomoku-board (-= left depl))))
  1183.     (-= a 1))
  1184.   (a+4 (+ a 4))
  1185.   (while
  1186.     (and (< b a+4)        ; stretch tuple right
  1187.      (== value (gomoku-board (+= right depl))))
  1188.     (+= b 1))
  1189.   (if (== b a+4)            ; tuple length = 5 ?
  1190.     {
  1191.       (gomoku-winning-qtuple-beg (+ square (* a depl)))
  1192.       (gomoku-winning-qtuple-end (+ square (* b depl)))
  1193.       (gomoku-winning-qtuple-dx dx)
  1194.       (gomoku-winning-qtuple-dy dy)
  1195.       TRUE
  1196.     }
  1197.     FALSE)
  1198. })
  1199.  
  1200.   ;; Cross winning qtuple, as found by gomoku-find-filled-qtuple.
  1201. (defun gomoku-cross-winning-qtuple
  1202. {
  1203.   (gomoku-cross-qtuple gomoku-winning-qtuple-beg
  1204.                gomoku-winning-qtuple-end
  1205.                gomoku-winning-qtuple-dx
  1206.                gomoku-winning-qtuple-dy)
  1207. })
  1208.  
  1209.   ;; Cross every square between SQUARE1 and SQUARE2 in the DX, DY direction.
  1210. (defun gomoku-cross-qtuple (int sq1 square2 dx dy)
  1211. {
  1212.   (int depl n col square1)
  1213.  
  1214.   (square1 sq1)
  1215.   (set-mark)            ; Not moving point from last square
  1216.   (depl (gomoku-xy-to-index dx dy))
  1217.       ;; WARNING: this function assumes DEPL > 0 and SQUARE2 > SQUARE1
  1218.   (while (not (== square1 square2))
  1219.   {
  1220.     (gomoku-goto-square square1)
  1221.     (+= square1 depl)
  1222.     (case
  1223.       (and (== dx 1) (== dy 0))        ; Horizontal
  1224.       {
  1225.     (n 1)
  1226.     (while (< n gomoku-square-width)
  1227.     {
  1228.       (+= n 1)
  1229.       (next-character)
  1230.       (gomoku-put-char "-")
  1231.     })
  1232.       }
  1233.       (and (== dx 0) (== dy 1))        ; Vertical
  1234.       {
  1235.     (n 1)(col (current-column))
  1236.     (while (< n gomoku-square-height)
  1237.     {
  1238.       (+= n 1)
  1239.       (forward-line 1)
  1240.       (to-col col)
  1241.       (insert-text "|")
  1242.     })
  1243.       }
  1244.       (and (== dx -1) (== dy 1))    ; 1st Diagonal
  1245.       {
  1246.     (arg-prefix (/ gomoku-square-width 2))(previous-character)
  1247.     (col (current-column))
  1248.     (forward-line (/ gomoku-square-height 2))
  1249.     (to-col col)
  1250.     (insert-text "/")
  1251.       }
  1252.       (and (== dx 1) (== dy 1))        ; 2nd Diagonal
  1253.       {
  1254.     (next-character (/ gomoku-square-width 2))
  1255.     (col (current-column))
  1256.     (forward-line (/ gomoku-square-height 2))
  1257.     (to-col col)
  1258.     (insert-text "\\")
  1259.       }
  1260.     )
  1261.   })
  1262.   (exchange-dot-and-mark)
  1263.   (update)                ; Display NOW
  1264. })
  1265.  
  1266. ;;;
  1267. ;;; CURSOR MOTION.
  1268. ;;;
  1269.   ;; Move point backward one column on the Gomoku board.
  1270. (defun gomoku-move-left
  1271. {
  1272.   (int x)
  1273.  
  1274.   (x (gomoku-point-x))
  1275.   (arg-prefix
  1276.     (case
  1277.       (== 0 x) 1
  1278.       (> x 1) gomoku-square-width
  1279.       TRUE 0
  1280.     ))
  1281.   (previous-character)
  1282. })
  1283.  
  1284.   ;; Move point forward one column on the Gomoku board.
  1285. (defun gomoku-move-right
  1286. {
  1287.   (int x)
  1288.  
  1289.   (x (gomoku-point-x))
  1290.   (arg-prefix
  1291.     (case
  1292.       (== x 0) 1
  1293.       (< x gomoku-board-width) gomoku-square-width
  1294.       TRUE 0
  1295.     ))
  1296.   (next-character)
  1297. })
  1298.  
  1299.   ;; Move point down one row on the Gomoku board.
  1300. (defun gomoku-move-down
  1301. {
  1302.   (int x y)
  1303.  
  1304.   (y (gomoku-point-y))(x (current-column))
  1305.   
  1306.   (forward-line
  1307.     (case
  1308.       (== 0 y) 1
  1309.       (< y gomoku-board-height) gomoku-square-height
  1310.       TRUE 0
  1311.     ))
  1312.   (current-column x)
  1313. })
  1314.  
  1315.   ;; Move point up one row on the Gomoku board.
  1316. (defun gomoku-move-up
  1317. {
  1318.   (int x y)
  1319.  
  1320.   (y (gomoku-point-y))(x (current-column))
  1321.  
  1322.   (forward-line
  1323.     (- 0
  1324.       (case
  1325.         (== 0 y) 1
  1326.     (> y 1) gomoku-square-height
  1327.     TRUE 0
  1328.       )))
  1329.   (current-column x)
  1330. })
  1331.  
  1332.   ;; Move point North East on the Gomoku board.
  1333. (defun gomoku-move-ne { (gomoku-move-up) (gomoku-move-right) })
  1334.  
  1335.   ;; Move point South East on the Gomoku board.
  1336. (defun gomoku-move-se { (gomoku-move-down) (gomoku-move-right) })
  1337.  
  1338.   ;; Move point North West on the Gomoku board.
  1339. (defun gomoku-move-nw { (gomoku-move-up) (gomoku-move-left) })
  1340.  
  1341.   ;; Move point South West on the Gomoku board.
  1342. (defun gomoku-move-sw { (gomoku-move-down) (gomoku-move-left) })
  1343.